﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Mvc;
using CID.Domain.Development;
using CID.Domain.Infrastructure;
using CID.Domain.Operations;
using CID.Web.Models.Infrastructure;
using Newtonsoft.Json;
using Environment = CID.Domain.Operations.Environment;

namespace CID.Web.Models.Development
{
    public class VersionViewModel : BaseDescribedViewModel<IVersion>, IAjaxEnabledViewModel
    {
        #region Properties
        [HiddenInput(DisplayValue=false)]
        [Required]
        public int ComponentId { get; set; }
        public string ComponentName { get; set; }

        [HiddenInput(DisplayValue = false)]
        public int? ReleaseId { get; set; }
        public string Release { get; set; }

        [HiddenInput(DisplayValue = false)]
        [Required(ErrorMessage="You must provide a Deployment Template")]
        public int? DeploymentTemplateId { get; set; }
        [Display(Name = "Deployment Template")]
        public string DeploymentTemplate { get; set; }
        [HiddenInput(DisplayValue = false)]
        public int? OriginalDeploymentTemplateId { get; set; }

        public List<ParameterValueViewModel> DeploymentParameterValues { get; set; }

        [Display(Name = "Depends On")]
        public List<DependencyComponentViewModel> DependsOn { get; set; }

        public List<PackageViewModel> Packages { get; set; }

        public string PostUrl { get; set; }
        #endregion

        public VersionViewModel() : base() {
            DeploymentParameterValues = new List<ParameterValueViewModel>();
            DependsOn = new List<DependencyComponentViewModel>();
            Packages = new List<PackageViewModel>();
        }

        public VersionViewModel(IVersion model, params IEnvironment[] environments) : base(model)
        {
            DeploymentParameterValues = new List<ParameterValueViewModel>();
            DependsOn = model.DependsOn.Select(x => new DependencyComponentViewModel(x)).ToList();
            Packages = model.Packages.Select(x => new PackageViewModel(x)).ToList();
            SetComponentData(model.Component);

            if (model.Release != null)
            {
                ReleaseId = model.Release.Id;
                Release = model.Release.Name;
            }

            if (model.DeploymentDefinition.DeploymentTemplate != null)
            {
                DeploymentTemplateId = model.DeploymentDefinition.DeploymentTemplate.Id;
                DeploymentTemplate = model.DeploymentDefinition.DeploymentTemplate.Name;
                foreach (var parameterValue in model.DeploymentDefinition.DefaultParameterValues)
                    DeploymentParameterValues.Add(new ParameterValueViewModel(parameterValue));
                foreach (var parameterValue in model.DeploymentDefinition.EnvironmentParameterValues)
                    DeploymentParameterValues.Add(new ParameterValueViewModel(parameterValue));

                foreach (var environment in environments)
                    foreach (var parameter in model.DeploymentDefinition.DeploymentTemplate.Parameters)
                        if(model.DeploymentDefinition.GetEnvironmentParameterValue(parameter,environment) == null)
                            DeploymentParameterValues.Add(new ParameterValueViewModel(parameter, environment));
            }
            OriginalDeploymentTemplateId = DeploymentTemplateId;
        }

        public VersionViewModel(IComponent component) : this()
        {
            SetComponentData(component);
        }

        private void SetComponentData(IComponent component)
        {
            if (component == null) throw new ArgumentNullException("The component can't be null");
            ComponentId = component.Id;
            ComponentName = component.Name;
        }

        public override void UpdateModel(IVersion model)
        {
            base.UpdateModel(model);
            
            model.DeploymentDefinition.DeploymentTemplate = availableDeploymentTemplates.SingleOrDefault(x => x.Id == DeploymentTemplateId);

            //Only push changes to the parameters if the deployment template is not null and has not changed.
            if ((model.DeploymentDefinition.DeploymentTemplate != null) && (model.DeploymentDefinition.DeploymentTemplate.Id == OriginalDeploymentTemplateId))
                UpdateDeploymentParameterValues(model);

            UpdateRelease(model);

            UpdateDependencies(model);
        }

        private void UpdateDeploymentParameterValues(IVersion model)
        {
            var parameterValuesPendingUpdate = model.DeploymentDefinition.DefaultParameterValues.Cast<IParameterValue>().Concat(model.DeploymentDefinition.EnvironmentParameterValues).ToList();
            foreach (var parameterValueViewModel in DeploymentParameterValues.Where(x => x.HasValue))
            {
                IParameterValue parameterValue = null;
                var parameter = model.DeploymentDefinition.DeploymentTemplate.Parameters.SingleOrDefault(x => x.Id == parameterValueViewModel.ParameterId);
                if (!parameterValueViewModel.EnvironmentId.HasValue)
                    parameterValue = model.DeploymentDefinition.GetDefaultParameterValue(parameter);
                else
                {
                    var environment = availableEnvironments.SingleOrDefault(x => x.Id == parameterValueViewModel.EnvironmentId.Value);
                    parameterValue = model.DeploymentDefinition.GetEnvironmentParameterValue(parameter, environment);
                    if (parameterValue == null) parameterValue = model.DeploymentDefinition.AddEnvironmentParameterValue(parameter, environment);
                }

                if (parameterValue != null)
                {
                    parameterValueViewModel.UpdateModel(parameterValue);
                    parameterValuesPendingUpdate.Remove(parameterValue);
                }
            }

            foreach (var parameterValue in parameterValuesPendingUpdate.Where(x => x is IEnvironmentParameterValue).Cast<IEnvironmentParameterValue>())
                model.DeploymentDefinition.RemoveEnvironmentParameterValue(parameterValue);
            //TODO: check if a default parameter value is to be removed and throwexception
        }

        private void UpdateRelease(IVersion model)
        {
            if (!ReleaseId.HasValue)
            {
                if (model.Release != null)
                    model.Release.RemoveVersion(model);
            }
            else
            {
                var release = availableReleases.SingleOrDefault(x => x.Id == ReleaseId);
                if (model.Release == null)
                    release.AddVersion(model);
                else if (model.Release.Id != ReleaseId.Value)
                {
                    model.Release.RemoveVersion(model);
                    release.AddVersion(model);
                }
            }
        }

        private void UpdateDependencies(IVersion model)
        {
            var selectedDependenciesIds = DependsOn.Select(x => x.Id).ToArray();
            var dependenciesToRemove = model.DependsOn.Where(x => !selectedDependenciesIds.Contains(x.Id)).ToArray();
            var dependenciesToAddIds = selectedDependenciesIds.Where(x => model.DependsOn.Count(y => y.Id == x) == 0).ToArray();

            foreach (var dependency in dependenciesToRemove)
                model.RemoveDepedency(dependency);
            foreach (var dependency in availableDependencies.Where(x => dependenciesToAddIds.Contains(x.Id)))
                model.AddDependency(dependency);
        }

        #region Edition helper members
        #region Releases
        private IEnumerable<Release> availableReleases;
        
        public void SetAvailableReleases(IEnumerable<Release> availableReleases)
        {
            this.availableReleases = availableReleases;
        }

        [JsonIgnore]
        public IEnumerable<ReleaseViewModel> AvailableReleases
        {
            get { return availableReleases.ToList().Select(x => new ReleaseViewModel(x)); }
        }
        #endregion

        #region Deployment Templates
        private IEnumerable<DeploymentTemplate> availableDeploymentTemplates;

        public void SetAvailableDeploymentTemplates(IEnumerable<DeploymentTemplate> availableDeploymentTemplates)
        {
            this.availableDeploymentTemplates = availableDeploymentTemplates;
        }

        [JsonIgnore]
        public IEnumerable<TemplateViewModel> AvailableDeploymentTemplates
        {
            get { return availableDeploymentTemplates.ToList().Select(x => new TemplateViewModel(x)); }
        }
        #endregion

        #region Environments
        private IEnumerable<Environment> availableEnvironments;

        public void SetAvailableEnvironments(IEnumerable<Environment> availableEnvironments)
        {
            this.availableEnvironments = availableEnvironments;
        }
        #endregion

        #region Dependencies
        private IEnumerable<Component> availableDependencies;
        public void SetAvailableDependencies(IEnumerable<Component> availableDependencies)
        {
            this.availableDependencies = availableDependencies;
            AvailableDependencies = availableDependencies.Select(x => new DependencyComponentViewModel(x)).ToList();
        }

        public List<DependencyComponentViewModel> AvailableDependencies { get; set; }
        #endregion
        #endregion
    }
}